Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@watchable/unpromise
Advanced tools
Promise with unsubscribe feature that minimises memory leaks
Javascript's built-in implementation of
Promise.race
and
Promise.any
have a bug/feature that leads to
uncontrollable memory leaks.
See the Typical Problem Case
below for reference.
The Memory leaks are fixed by using @watchable/unpromise.
In general the Promise
API doesn't allow for an unsubscription model. The
@watchable/unpromise
package wraps individual promises to provide an
unsubscribe method. It uses this approach to provide safe implementations of
Unpromise.race
and Unpromise.any
. However, the ability to unsubscribe
Promises may be useful for other cases where the Promise reference chains (and
therefore memory leaks) are otherwise out of your control.
Substitute Unpromise.race
or Unpromise.any
in place of Promise.race
and
Promise.any
...
import { Unpromise } from "@watchable/unpromise";
const raceResult = await Unpromise.race([taskPromise, interruptPromise]);
const anyResult = await Unpromise.any([taskPromise, interruptPromise]);
Advanced users exploring other async/await patterns should consider
Unpromise.proxy()
or Unpromise.resolve()
. Read more at the
API docs.
npm install @watchable/unpromise
import { Unpromise } from "@watchable/unpromise"; // esm build
const { Unpromise } = require("@watchable/unpromise"); // commonjs build
The library manages a single lazy-created ProxyPromise
for you that shadows
any Promise
. For every native Promise there is only one ProxyPromise
. It
remains cached in a WeakMap for the lifetime of the Promise itself. On creation,
the shadow ProxyPromise
adds handlers to the native Promise's .then()
and
.catch()
just once. This eliminates memory leaks from adding multiple
handlers.
const proxyPromise = Unpromise.proxy(promise);
As an alternative if you are constructing your own Promise
, you can use
Unpromise
to create a ProxyPromise
right from the beginning...
const proxyPromise = new Unpromise((resolve) => setTimeout(resolve, 1000));
Once you have a ProxyPromise
you can call proxyPromise.then()
proxyPromise.catch()
or proxyPromise.finally()
in the normal way. A promise
returned by these methods is a SubscribedPromise
. It behaves like any normal
Promise
except it has an unsubscribe()
method that will remove its handlers
from the ProxyPromise
.
Finally you must call subscribedPromise.unsubscribe()
before you release the
promise reference. This eliminates memory leaks from subscription and
(therefore) from reference retention.
Using Unpromise.race()
or Unpromise.any()
is recommended. Using these static
methods, the proxying, subscribing and unsubscribing steps are handled behind
the scenes for you automatically.
Alternatively const subscribedPromise = Unpromise.resolve(promise)
completes
both Step 1 and Step 2 for you (it's equivalent to
const subscribedPromise = Unpromise.proxy(promise).subscribe()
). Then later
you can call subscribedPromise.unsubscribe()
to tidy up.
In the example app below, we have a long-lived Promise that we await every time
around the loop with Promise.race(...)
. We use race
so that we can respond
to either the task result or the keyboard interrupt.
Unfortunately this leads to a memory leak. Every call to Promise.race
creates
an unbreakable reference chain from the interruptPromise
to the taskPromise
(and its task result), and these references can never be garbage-collected,
leading to an out of memory error.
const interruptPromise = new Promise((resolve) => {
process.once("SIGINT", () => resolve("interrupted"));
});
async function run() {
let count = 0;
for (; ; count++) {
const taskPromise = new Promise((resolve) => {
// an imaginary task
setImmediate(() => resolve("task_result"));
});
const result = await Promise.race([taskPromise, interruptPromise]);
if (result === "interrupted") {
break;
}
console.log(`Completed ${count} tasks`);
}
console.log(`Interrupted by user`);
}
run();
FAQs
Promise with unsubscribe feature that minimises memory leaks
We found that @watchable/unpromise demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.